// Copyright 2011 Google Inc. All Rights Reserved.
// Author: jacobsa@google.com (Aaron Jacobs)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

var fi = flame.internal;
var processResolveWork = fi.processResolveWork;

//////////////////////////////////////////////////////
// Helpers
//////////////////////////////////////////////////////

// Compare actions by reference.
flame.Action.gjstestEquals = function(other) { return this === other; };

function makeItem(actionNames, callback) {
  return { 'actionNames': actionNames, 'callback': callback };
}

function registerActions(actions) {
  return function() {
    actions.forEach(function(action) {
      fi.registeredActions.insert(action.name, action);
    });
  }
}

function pushResolveWork(item) {
  return function() { fi.resolveWork.push(item); }
}

function ProcessResolveWorkTest() {
  // Create callback functions.
  this.callback1_ = createMockFunction('callback1');
  this.callback2_ = createMockFunction('callback2');
  this.callback3_ = createMockFunction('callback3');

  // Mock out some functions.
  this.processFlameFile_ = fi.processFlameFile;
  fi.processFlameFile = createMockFunction('processFlameFile');
}
registerTestSuite(ProcessResolveWorkTest);

ProcessResolveWorkTest.prototype.tearDown = function() {
  // Replace any global state we modified in the test.
  fi.currentPackageName = null;
  fi.registeredActions = new flame.internal.StringMap;
  fi.outputFileIndex = new flame.internal.StringMap;
  fi.resolveWork = [];

  fi.processFlameFile = this.processFlameFile_;
};

//////////////////////////////////////////////////////
// Tests
//////////////////////////////////////////////////////

ProcessResolveWorkTest.prototype.NoWorkRegistered = function() {
  // Shouldn't throw an error.
  processResolveWork();

  expectThat(fi.resolveWork, elementsAre([]));
};

ProcessResolveWorkTest.prototype.ItemWithoutDependencies = function() {
  // Work
  fi.resolveWork.push(makeItem([], this.callback1_));

  // Callbacks
  expectCall(this.callback1_)(recursivelyEquals({}));

  // Call
  processResolveWork();

  expectThat(fi.resolveWork, elementsAre([]));
};

ProcessResolveWorkTest.prototype.OneItemAlreadyProcessed = function() {
  // Pretend that some actions have already been processed.
  var action1 = new flame.Action(':taco');
  var action2 = new flame.Action(':burrito');

  fi.registeredActions.insert(':taco', action1);
  fi.registeredActions.insert(':burrito', action2);

  // Work
  fi.resolveWork.push(makeItem([':taco', ':burrito'], this.callback1_));

  // Callbacks
  expectCall(this.callback1_)(
      recursivelyEquals({':taco': action1, ':burrito': action2}));

  // Call
  processResolveWork();

  expectThat(fi.resolveWork, elementsAre([]));
};

ProcessResolveWorkTest.prototype.OneItemWithTwoPackageDeps = function() {
  // Work
  fi.resolveWork.push(makeItem(['foo:taco', 'bar:burrito'], this.callback1_));

  // processFlameFile
  var action1 = new flame.Action('foo:taco');
  var action2 = new flame.Action('bar:burrito');

  expectCall(fi.processFlameFile)('foo')
      .willOnce(registerActions([action1]));

  expectCall(fi.processFlameFile)('bar')
      .willOnce(registerActions([action2]));

  // Callbacks
  expectCall(this.callback1_)(
      recursivelyEquals({
          'foo:taco': action1,
          'bar:burrito': action2,
      }));

  // Call
  processResolveWork();

  expectThat(fi.resolveWork, elementsAre([]));
};

ProcessResolveWorkTest.prototype.OneItemWithNonExistentAction = function() {
  // Work
  fi.resolveWork.push(makeItem(['foo:burrito'], this.callback1_));

  // processFlameFile -- don't register foo:burrito.
  var action1 = new flame.Action('foo:taco');
  var action2 = new flame.Action('foo:enchilada');

  expectCall(fi.processFlameFile)('foo')
      .willOnce(registerActions([action1, action2]));

  // Call
  expectThat(processResolveWork, throwsError(/Unknown.*action.*foo:burrito/));
};

ProcessResolveWorkTest.prototype.DedupesPackageNames = function() {
  // Work
  fi.resolveWork.push(makeItem(['foo:taco', 'foo:burrito'], this.callback1_));

  // processFlameFile -- should only be called once.
  var action1 = new flame.Action('foo:taco');
  var action2 = new flame.Action('foo:burrito');

  expectCall(fi.processFlameFile)('foo')
      .willOnce(registerActions([action1, action2]));

  // Callbacks
  expectCall(this.callback1_)(_);

  // Call
  processResolveWork();
};

ProcessResolveWorkTest.prototype.MultipleItemsFullyProcessed = function() {
  // Work
  fi.resolveWork.push(makeItem(['foo:taco', 'bar:burrito'], this.callback1_));
  fi.resolveWork.push(makeItem(['baz:enchilada'], this.callback2_));

  // processFlameFile
  var action1 = new flame.Action('foo:taco');
  var action2 = new flame.Action('bar:burrito');
  var action3 = new flame.Action('baz:enchilada');

  expectCall(fi.processFlameFile)('foo')
      .willOnce(registerActions([action1]));

  expectCall(fi.processFlameFile)('bar')
      .willOnce(registerActions([action2]));

  expectCall(fi.processFlameFile)('baz')
      .willOnce(registerActions([action3]));

  // Callbacks
  expectCall(this.callback1_)(
      recursivelyEquals({
          'foo:taco': action1,
          'bar:burrito': action2,
      }));

  expectCall(this.callback2_)(
      recursivelyEquals({
          'baz:enchilada': action3,
      }));

  // Call
  processResolveWork();

  expectThat(fi.resolveWork, elementsAre([]));
};

ProcessResolveWorkTest.prototype.EarlyItemDependsOnLater = function() {
  // Work
  fi.resolveWork.push(makeItem(['foo:enchilada'], this.callback3_));

  // processFlameFile -- make foo:burrito depend on foo:taco, and foo:enchilada
  // depend on both.
  var action1 = new flame.Action('foo:taco');
  var action2 = new flame.Action('foo:burrito');
  var action3 = new flame.Action('foo:enchilada');

  expectCall(fi.processFlameFile)('foo')
      .willOnce(
          doAll([
              pushResolveWork(
                  makeItem(
                      ['foo:taco', 'foo:burrito'],
                      function() { flame.registerAction(action3); })),
              pushResolveWork(
                  makeItem(
                      ['foo:taco'],
                      function() { flame.registerAction(action2); })),
              registerActions([action1]),
          ]));

  // Callback
  expectCall(this.callback3_)(
      recursivelyEquals({
          'foo:enchilada': action3,
      }));

  // Call
  processResolveWork();

  expectThat(fi.resolveWork, elementsAre([]));
};

ProcessResolveWorkTest.prototype.DoesntProcessSamePackageTwice = function() {
  // Work -- depend on foo from both targets.
  fi.resolveWork.push(makeItem(['foo:taco', 'bar:burrito'], this.callback1_));
  fi.resolveWork.push(makeItem(['foo:enchilada'], this.callback2_));

  // processFlameFile -- shouldn't be called twice for foo.
  var action1 = new flame.Action('foo:taco');
  var action2 = new flame.Action('bar:burrito');
  var action3 = new flame.Action('foo:enchilada');

  expectCall(fi.processFlameFile)('foo')
      .willOnce(registerActions([action1, action3]));

  expectCall(fi.processFlameFile)('bar')
      .willOnce(registerActions([action2]));

  // Callbacks
  expectCall(this.callback1_)(_);
  expectCall(this.callback2_)(_);

  // Call
  processResolveWork();
};

ProcessResolveWorkTest.prototype.ProcessingPackageAddsMoreItems = function() {
  var item1 = makeItem(['foo:taco'], this.callback1_);
  var item2 = makeItem(['bar:burrito', 'foo:taco'], this.callback2_);

  // Work -- add only the first item for now.
  fi.resolveWork.push(item1);

  // processFlameFile
  var action1 = new flame.Action('foo:taco');
  var action2 = new flame.Action('bar:burrito');

  expectCall(fi.processFlameFile)('foo')
      .willOnce(doAll([registerActions([action1]), pushResolveWork(item2)]));

  expectCall(fi.processFlameFile)('bar')
      .willOnce(registerActions([action2]));

  // Callbacks
  expectCall(this.callback1_)(
      recursivelyEquals({
          'foo:taco': action1,
      }));

  expectCall(this.callback2_)(
      recursivelyEquals({
          'foo:taco': action1,
          'bar:burrito': action2,
      }));

  // Call
  processResolveWork();
};

ProcessResolveWorkTest.prototype.NonExistentActionAfterMultipleLevels =
    function() {
  var item1 = makeItem(['foo:taco'], this.callback1_);
  var item2 = makeItem(['bar:burrito'], this.callback2_);
  var item3 = makeItem(['baz:enchilada'], this.callback3_);

  // Work -- add only the first item for now.
  fi.resolveWork.push(item1);

  // processFlameFile
  var action1 = new flame.Action('foo:taco');
  var action2 = new flame.Action('bar:burrito');

  expectCall(fi.processFlameFile)('foo')
      .willOnce(doAll([registerActions([action1]), pushResolveWork(item2)]));

  expectCall(fi.processFlameFile)('bar')
      .willOnce(doAll([registerActions([action2]), pushResolveWork(item3)]));

  expectCall(fi.processFlameFile)('baz');

  // Callbacks
  expectCall(this.callback1_)(_);
  expectCall(this.callback2_)(_);

  // Call
  expectThat(processResolveWork, throwsError(/Unknown.*action.*baz:enchilada/));
};
